﻿namespace Resharper.Abstractions.Actions
{
  using System;
  using System.Diagnostics;
  using JetBrains.Annotations;
  using JetBrains.Application;
  using JetBrains.Application.Progress;
  using JetBrains.ProjectModel;
  using JetBrains.ReSharper.Feature.Services.Bulbs;
  using JetBrains.ReSharper.Intentions.Extensibility;
  using JetBrains.TextControl;
  using JetBrains.Threading;
  using JetBrains.Util;

  /// <summary>Defines the <see cref="ContextActionBase"/> class.</summary>
  public abstract class ContextActionBase : BulbItemImpl, IContextAction
  {
    #region Constructors and Destructors

    /// <summary>Initializes a new instance of the <see cref="ContextActionBase"/> class.</summary>
    /// <param name="provider">The provider.</param>
    protected ContextActionBase([NotNull] IContextActionDataProvider provider)
    {
      if (provider == null)
      {
        throw new ArgumentNullException("provider");
      }

      this.InternalProvider = provider;
    }

    #endregion

    #region Properties

    /// <summary>
    /// Popup menu item text
    /// </summary>
    public override string Text
    {
      get
      {
        return this.GetText();
      }
    }

    /// <summary>The provider field.</summary>
    [NotNull]
    internal IContextActionDataProvider InternalProvider { get; private set; }

    /// <summary>
    /// Gets or sets the tag.
    /// </summary>
    /// <value>The tag.</value>
    private object Tag { get; set; }

    #endregion

    #region Public Methods

    /// <summary>Fires the action.</summary>
    /// <param name="context">The context.</param>
    public void Fire(ExecuteContext context)
    {
      this.Tag = context.Tag;
      
      var textControl = context.InternalTextControl;
      Debug.Assert(textControl != null, "textControl != null");

      Shell.Instance.GetComponent<IThreading>().ReentrancyGuard.ExecuteOrQueue(this.Text, () => this.Execute(context.Solution.InternalSolution, textControl));
    }

    #endregion

    #region Implemented Interfaces

    #region IBulbAction

    /// <summary>Check if this action is available at the constructed context.
    /// Actions could store precalculated info in <paramref name="cache"/> to share it between different actions</summary>
    /// <param name="cache">The cache.</param>
    /// <returns><c>true</c> if the specified cache is available; otherwise, <c>false</c>.</returns>
    public bool IsAvailable(IUserDataHolder cache)
    {
      if (cache == null)
      {
        throw new ArgumentNullException("cache");
      }

      this.Tag = null;

      var selectedElement = this.InternalProvider.SelectedElement;
      if (selectedElement == null)
      {
        return false;
      }

      if (selectedElement.Language.Name != "CSHARP")
      {
        return false;
      }

      return this.CanExecute(new CanExecuteContext(this.InternalProvider, cache));
    }

    #endregion

    #endregion

    #region Methods

    /// <summary>Determines whether this instance can execute the specified action context.</summary>
    /// <param name="context">The action context.</param>
    /// <returns><c>true</c> if this instance can execute the specified action context; otherwise, <c>false</c>.</returns>
    protected abstract bool CanExecute([NotNull] CanExecuteContext context);

    /// <summary>Executes the action.</summary>
    /// <param name="context">The execute context.</param>
    protected abstract void Execute([NotNull] ExecuteContext context);

    /// <summary>Executes after the action.</summary>
    /// <param name="context">The context.</param>
    protected virtual void PostExecute(ExecuteContext context)
    {
    }

    /// <summary>Executes QuickFix or ContextAction. Returns post-execute method.</summary>
    /// <param name="solution">The solution.</param>
    /// <param name="progress">The progress.</param>
    /// <returns>Action to execute after document and PSI transaction finish. Use to open TextControls, navigate caret, etc.</returns>
    protected override Action<ITextControl> ExecutePsiTransaction(ISolution solution, IProgressIndicator progress)
    {
      var context = new ExecuteContext(this.InternalProvider, solution, this.Tag);

      this.Execute(context);

      return delegate(ITextControl textControl)
      {
        context.InternalTextControl = textControl;
        this.PostExecute(context);
      };
    }

    /// <summary>Gets the text.</summary>
    /// <returns>Returns the text.</returns>
    [NotNull]
    protected abstract string GetText();

    #endregion
  }
}